package cn.dansj.common.utils.redis;

import cn.dansj.common.utils.bean.DBConnection;
import cn.dansj.common.utils.json.Feature;
import cn.dansj.common.utils.json.JSONUtils;
import cn.dansj.common.utils.json.JSONObject;
import cn.dansj.common.utils.reflect.ClassUtils;
import cn.dansj.common.utils.transfer.ArrayUtils;
import cn.dansj.common.utils.transfer.AssertUtils;
import cn.dansj.common.utils.transfer.Transformation;
import cn.dansj.common.utils.transfer.Verification;
import io.lettuce.core.*;
import io.lettuce.core.cluster.ClusterClientOptions;
import io.lettuce.core.cluster.ClusterTopologyRefreshOptions;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.*;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.data.redis.core.*;

import java.io.Closeable;
import java.net.URI;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

public class RedisUtils {
    private static final Logger logger = LoggerFactory.getLogger(RedisUtils.class);

    private static final Map<DBConnection, LettuceConnectionFactory> redisConnectionFactoryCache = new ConcurrentHashMap<>();

    private final StringRedisTemplate redisTemplate;

    public RedisUtils(StringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    public boolean hasKey(String key) {
        return redisTemplate.hasKey(key);
    }

    public String getString(String key) {
        return redisTemplate.opsForValue().get(key);
    }

    public List<String> getList(String key, int start, int end) {
        return redisTemplate.opsForList().range(key, start, end);
    }

    public long rightPush(String key, List<String> value) {
        return longIfNull(redisTemplate.opsForList().rightPushAll(key, value));
    }

    private long longIfNull(Long l) {
        return Transformation.requireNonNullElseGet(l, () -> 0L);
    }

    public long size(String key, DataType dataType) {
        if (dataType == null) return 0;
        if (dataType.equals(DataType.HASH)) {
            return redisTemplate.opsForHash().size(key);
        } else if (dataType.equals(DataType.LIST)) {
            return longIfNull(redisTemplate.opsForList().size(key));
        } else if (dataType.equals(DataType.SET)) {
            return longIfNull(redisTemplate.opsForSet().size(key));
        } else if (dataType.equals(DataType.ZSET)) {
            return longIfNull(redisTemplate.opsForZSet().size(key));
        } else if (dataType.equals(DataType.STREAM)) {
            return longIfNull(redisTemplate.opsForStream().size(key));
        } else if (dataType.equals(DataType.STRING)) {
            return longIfNull(redisTemplate.opsForValue().size(key));
        }
        throw new IllegalArgumentException("未知的数据类型:" + dataType.name());
    }

    public long size(String key) {
        return size(key, redisTemplate.type(key));
    }

    public List<String> getList(String key) {
        return getList(key, 0, -1);
    }

    public boolean set(final String key, String value) {
        boolean result = false;
        try {
            redisTemplate.opsForValue().set(key, value);
            result = true;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return result;
    }

    public boolean set(final String key, String value, long t, TimeUnit timeUnit) {
        boolean result = false;
        try {
            redisTemplate.opsForValue().set(key, value, t, timeUnit);
            result = true;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return result;
    }

    public boolean setHash(final String key, Object hashKey, Object hashValue) {
        if (Verification.checkAnyNull(key, hashKey)) return false;
        boolean result = false;
        try {
            redisTemplate.opsForHash().put(key, hashKey, hashValue);
            result = true;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return result;
    }

    public boolean setHash(final String key, Object hashKey, Object hashValue, int expire) {
        if (expire <= 0) return setHash(key, hashKey, hashValue);

        SessionCallback<Boolean> callback = new SessionCallback<Boolean>() {
            @Override
            public <K, V> Boolean execute(RedisOperations<K, V> operations) throws DataAccessException {
                try {
                    operations.multi();
                    setHash(key, hashKey, hashValue);
                    expire(key, expire);
                    operations.exec();
                    return true;
                } catch (Exception e) {
                    return false;
                }
            }
        };
        return redisTemplate.execute(callback);
    }

    public boolean setHash(final String key, Object hashKey, Object hashValue, long expire) {
        if (expire <= 0) return setHash(key, hashKey, hashValue);

        SessionCallback<Boolean> callback = new SessionCallback<Boolean>() {
            @Override
            public <K, V> Boolean execute(RedisOperations<K, V> operations) throws DataAccessException {
                try {
                    operations.multi();
                    setHash(key, hashKey, hashValue);
                    expire(key, expire);
                    operations.exec();
                    return true;
                } catch (Exception e) {
                    return false;
                }
            }
        };
        return redisTemplate.execute(callback);
    }

    public boolean setHash(final String key, Object hashKey, Object hashValue, Date expire) {
        if (expire == null) return setHash(key, hashKey, hashValue);

        SessionCallback<Boolean> callback = new SessionCallback<Boolean>() {
            @Override
            public <K, V> Boolean execute(RedisOperations<K, V> operations) throws DataAccessException {
                try {
                    operations.multi();
                    setHash(key, hashKey, hashValue);
                    expire(key, expire);
                    operations.exec();
                    return true;
                } catch (Exception e) {
                    return false;
                }
            }
        };
        return redisTemplate.execute(callback);
    }

    public boolean setHash(final String key, Map<?, ?> mapValue) {
        boolean result = false;
        try {
            redisTemplate.opsForHash().putAll(key, mapValue);
            result = true;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return result;
    }

    public boolean setHash(final String key, Map<?, ?> mapValue, long expire) {
        if (expire <= 0) return setHash(key, mapValue);

        SessionCallback<Boolean> callback = new SessionCallback<Boolean>() {
            @Override
            public <K, V> Boolean execute(RedisOperations<K, V> operations) throws DataAccessException {
                try {
                    operations.multi();
                    setHash(key, mapValue);
                    expire(key, expire);
                    operations.exec();
                    return true;
                } catch (Exception e) {
                    return false;
                }
            }
        };
        return redisTemplate.execute(callback);
    }

    public boolean setHash(final String key, Map<?, ?> mapValue, int expire) {
        if (expire <= 0) return setHash(key, mapValue);

        SessionCallback<Boolean> callback = new SessionCallback<Boolean>() {
            @Override
            public <K, V> Boolean execute(RedisOperations<K, V> operations) throws DataAccessException {
                try {
                    operations.multi();
                    setHash(key, mapValue);
                    expire(key, expire);
                    operations.exec();
                    return true;
                } catch (Exception e) {
                    return false;
                }
            }
        };
        return redisTemplate.execute(callback);
    }

    public boolean setHash(final String key, Map<?, ?> mapValue, Date expire) {
        if (expire == null) return setHash(key, mapValue);

        SessionCallback<Boolean> callback = new SessionCallback<Boolean>() {
            @Override
            public <K, V> Boolean execute(RedisOperations<K, V> operations) throws DataAccessException {
                try {
                    operations.multi();
                    setHash(key, mapValue);
                    expire(key, expire);
                    operations.exec();
                    return true;
                } catch (Exception e) {
                    return false;
                }
            }
        };
        return redisTemplate.execute(callback);
    }

    public boolean rightPush(final String key, String value, int maxSize) {
        boolean result = false;
        try {
            if (maxSize > 0) {
                final long size = redisTemplate.opsForList().size(key);
                if (size > maxSize - 1) redisTemplate.opsForList().trim(key, size - maxSize + 1, size - 1);
            }
            redisTemplate.opsForList().rightPush(key, value);
            result = true;
        } catch (Exception ignore) {
        }
        return result;
    }

    public boolean rightPush(final String key, String value) {
        return rightPush(key, value, 0);
    }

    public boolean leftPush(final String key, String value, int maxSize) {
        boolean result = false;
        try {
            if (maxSize > 0) {
                final long size = redisTemplate.opsForList().size(key);
                if (size > maxSize - 1) redisTemplate.opsForList().trim(key, 0, maxSize - 2);
            }
            redisTemplate.opsForList().leftPush(key, value);
            result = true;
        } catch (Exception ignore) {
        }
        return result;
    }

    public boolean leftPush(final String key, String value) {
        return leftPush(key, value, 0);
    }

    public boolean delete(String key) {
        return redisTemplate.delete(key);
    }

    public Long deleteByPrefix(String prefix) {
        List<String> keys = keys(prefix);
        if (Verification.checkNotNull(keys)) {
            return delete(keys);
        }
        return 0L;
    }

    public Long delete(List<String> keys) {
        return redisTemplate.delete(keys);
    }

    public RedisConnectionFactory getFactory() {
        return redisTemplate.getConnectionFactory();
    }

    public String ignoreCase(String key) {
        StringBuilder finalKey = new StringBuilder();
        for (String k : key.split("")) {
            if (k.matches("[a-zA-Z]")) {
                finalKey.append("[").append(k.toLowerCase()).append(k.toUpperCase()).append("]");
            } else {
                finalKey.append(k);
            }
        }
        return finalKey.toString();
    }

    @SuppressWarnings("unchecked")
    public List<String> keys(String pattern, boolean ignoreCase, boolean vague) {
        List<String> allKeys = new ArrayList<>();
        ScanOptions options;
        if (Verification.checkNull(pattern)) {
            options = ScanOptions.scanOptions().build();
        } else {
            ScanOptions.ScanOptionsBuilder scanOptionsBuilder = ScanOptions.scanOptions();
            String u = (vague ? "*" : "") + (ignoreCase ? ignoreCase(pattern) : pattern) + (vague ? "*" : "");
            options = scanOptionsBuilder.match(u).count(Long.MAX_VALUE).build();
        }

        redisTemplate.afterPropertiesSet();
        try (Cursor<String> cursor = (Cursor<String>) redisTemplate.executeWithStickyConnection((RedisCallback<Closeable>) connection -> new ConvertingCursor<>(connection.keyCommands().scan(options), redisTemplate.getKeySerializer()::deserialize))) {
            Objects.requireNonNull(cursor).forEachRemaining(allKeys::add);
        } catch (Exception ignore) {
        }
        return allKeys;
    }

    public JSONObject getHash(String key) {
        return new JSONObject(redisTemplate.opsForHash().entries(key));
    }

    public Object getHash(String key, String field) {
        return redisTemplate.opsForHash().get(key, field);
    }

    public DataType type(String key) {
        return redisTemplate.type(key);
    }

    public Set<String> getMembers(String key) {
        return redisTemplate.opsForSet().members(key);
    }

    public boolean addMembers(String key, String... value) {
        return longIfNull(redisTemplate.opsForSet().add(key, value)) == value.length;
    }

    public boolean isMember(String key, Object obj) {
        return Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(key, obj));
    }

    public Set<String> getZSet(String key) {
        return redisTemplate.opsForZSet().range(key, 0, -1);
    }

    public List<String> keys() {
        return keys(null);
    }

    public List<String> keys(String pattern) {
        return keys(pattern, false, false);
    }

    public StringRedisTemplate getRedisTemplate() {
        return redisTemplate;
    }

    public void expire(String key, Date expire) {
        redisTemplate.expireAt(key, expire);
    }

    public void expire(String key, long expire) {
        if (expire < 0) {
            redisTemplate.persist(key);
        } else {
            redisTemplate.expire(key, expire, TimeUnit.MILLISECONDS);
        }
    }

    public void expire(String key, int expire) {
        if (expire < 0) {
            redisTemplate.persist(key);
        } else {
            redisTemplate.expire(key, expire, TimeUnit.SECONDS);
        }
    }

    public long getExpire(String key) {
        return longIfNull(redisTemplate.getExpire(key));
    }

    public static List<Map<String, Object>> search(String name, List<Map<String, Object>> list) {
        List<Map<String, Object>> results = new ArrayList<>();
        // Pattern pattern = Pattern.compile(name);
        //如果要求大小写不敏感，改成：
        Pattern pattern = Pattern.compile(name, Pattern.CASE_INSENSITIVE);
        for (Map<String, Object> map : list) {
            Matcher matcher = pattern.matcher(map.get("").toString());
            //匹配查询
            //matcher.matches()
            if (matcher.find()) {
                results.add(map);
            }
        }
        return results;
    }

    public long increment(String key, int initSize, int delta) {
        final ValueOperations<String, String> ops = redisTemplate.opsForValue();
        ops.setIfAbsent(key, String.valueOf(initSize));
        return longIfNull(ops.increment(key, delta <= 0 ? 1 : delta));
    }

    private static LettuceConnectionFactory initRedisConnectionFactory(DBConnection connection) {
        URI uri = URI.create(connection.getUrl());
        RedisURI redisURI = RedisURI.create(uri);
        RedisProperties redisProperties = new RedisProperties();
        redisProperties.setConnectTimeout(redisURI.getTimeout());
        redisProperties.setTimeout(redisURI.getTimeout());
        String password = connection.getPassword();

        redisProperties.setPassword(password);
        redisProperties.setUsername(connection.getUsername());

        boolean ssl = redisURI.toURI().getScheme().equals("rediss");
        redisProperties.getSsl().setEnabled(ssl);
        String[] parts = redisURI.getHost().split(",");
        //单例
        if (parts.length == 1) {
            redisProperties.setDatabase(redisURI.getDatabase());
            redisProperties.setHost(redisURI.getHost());
            redisProperties.setPort(redisURI.getPort());
        } else {
            //cluster
            RedisProperties.Cluster cluster = new RedisProperties.Cluster();
            cluster.setNodes(ArrayUtils.asList(parts));
            cluster.setMaxRedirects(3);
            redisProperties.setCluster(cluster);
        }

        return (LettuceConnectionFactory) getRedisConnectionFactory(redisProperties);
    }

    public static RedisConnectionFactory getRedisConnectionFactory(RedisProperties redisProperties) {
        RedisProperties.Cluster cluster = redisProperties.getCluster();
        RedisProperties.Sentinel sentinel = redisProperties.getSentinel();
        Duration shutdownTimeout = redisProperties.getLettuce().getShutdownTimeout();
        String redisUrl = redisProperties.getUrl();
        String username = redisProperties.getUsername();
        String password = redisProperties.getPassword();

        int maxIdle = 20;
        int minIdle = 20;
        long maxWait;
        int maxActive = 20;
        long timeBetweenEvictionRunsMillis;
        if (redisProperties.getLettuce().getPool() != null) {
            maxIdle = redisProperties.getLettuce().getPool().getMaxIdle();
            minIdle = redisProperties.getLettuce().getPool().getMinIdle();
            maxWait = redisProperties.getLettuce().getPool().getMaxWait().toMillis();
            maxActive = redisProperties.getLettuce().getPool().getMaxActive();
            timeBetweenEvictionRunsMillis = Transformation.lambdaTryCatch(() -> redisProperties.getLettuce().getPool().getTimeBetweenEvictionRuns().toMillis(), () -> 1000L);
        } else {
            maxWait = -1;
            timeBetweenEvictionRunsMillis = 1000;
        }

        GenericObjectPoolConfig<?> poolConfig = new GenericObjectPoolConfig<>();
        poolConfig.setMaxIdle(maxIdle);
        poolConfig.setMinIdle(minIdle);
        poolConfig.setMaxTotal(maxActive);

        if (ClassUtils.hasMethod(GenericObjectPoolConfig.class, "setMaxWait", Duration.class)) {
            Transformation.lambdaTryCatch(() -> ClassUtils.getMethod(GenericObjectPoolConfig.class, "setMaxWait", Duration.class).invoke(poolConfig, Duration.ofMillis(maxWait)));
        }
        if (ClassUtils.hasMethod(GenericObjectPoolConfig.class, "setTimeBetweenEvictionRuns", Duration.class)) {
            Transformation.lambdaTryCatch(() -> ClassUtils.getMethod(GenericObjectPoolConfig.class, "setTimeBetweenEvictionRuns", Duration.class).invoke(poolConfig, Duration.ofMillis(timeBetweenEvictionRunsMillis)));
        }
        if (ClassUtils.hasMethod(GenericObjectPoolConfig.class, "setMaxWaitMillis", long.class)) {
            Transformation.lambdaTryCatch(() -> ClassUtils.getMethod(GenericObjectPoolConfig.class, "setMaxWaitMillis", long.class).invoke(poolConfig, maxWait));
        }
        if (ClassUtils.hasMethod(GenericObjectPoolConfig.class, "setTimeBetweenEvictionRunsMillis", long.class)) {
            Transformation.lambdaTryCatch(() -> ClassUtils.getMethod(GenericObjectPoolConfig.class, "setTimeBetweenEvictionRunsMillis", long.class).invoke(poolConfig, timeBetweenEvictionRunsMillis));
        }

        LettuceConnectionFactory factory;

        if (cluster != null && !cluster.getNodes().isEmpty()) {
            logger.info("redis connect type: cluster");
            Integer maxRedirects = redisProperties.getCluster().getMaxRedirects();
            RedisClusterConfiguration redisClusterConfiguration = new RedisClusterConfiguration(cluster.getNodes());
            redisClusterConfiguration.setMaxRedirects(maxRedirects);
            redisClusterConfiguration.setUsername(username);
            redisClusterConfiguration.setPassword(password);
            ClusterTopologyRefreshOptions options = ClusterTopologyRefreshOptions.builder().enablePeriodicRefresh().enableAllAdaptiveRefreshTriggers().refreshPeriod(Duration.ofSeconds(5)).build();
            ClusterClientOptions clientOptions = ClusterClientOptions.builder().topologyRefreshOptions(options).build();
            LettucePoolingClientConfiguration lettuceClientConfiguration = LettucePoolingClientConfiguration.builder().commandTimeout(Duration.ofMillis(60000)).readFrom(ReadFrom.MASTER_PREFERRED).clientOptions(clientOptions).shutdownTimeout(shutdownTimeout).poolConfig(poolConfig).build();
            factory = new LettuceConnectionFactory(redisClusterConfiguration, lettuceClientConfiguration);
        } else if (sentinel != null && !sentinel.getNodes().isEmpty()) {
            logger.info("redis connect type: sentinel");
            RedisSentinelConfiguration redisSentinelConfiguration = new RedisSentinelConfiguration().master(sentinel.getMaster());
            redisSentinelConfiguration.setSentinels(sentinel.getNodes().stream().map(RedisUtils::fromString).collect(Collectors.toList()));
            redisSentinelConfiguration.setPassword(password);
            redisSentinelConfiguration.setSentinelPassword(sentinel.getPassword());
            factory = new LettuceConnectionFactory(redisSentinelConfiguration);
        } else if (redisUrl != null && !redisUrl.trim().isEmpty()) {
            Pattern pattern = Pattern.compile("^redis://(?:(?<user>[^:]*):(?<password>.+)@)?(?<host>[^:/@]+)(?::(?<port>\\d+))?(?:/(?<db>\\d+))?$");
            Matcher matcher = pattern.matcher(redisUrl);
            AssertUtils.state(matcher.matches(), "invalid redisUrl");
            // 提取各部分（若不存在则为null）
            String regUser = matcher.group("user");
            String regPassword = matcher.group("password");
            String regHost = matcher.group("host");
            String regPort = matcher.group("port");
            String regDb = matcher.group("db");

            if (Verification.checkNotNull(regUser)) redisProperties.setUsername(regUser);
            if (Verification.checkNotNull(regPassword)) redisProperties.setPassword(regPassword);
            if (Verification.checkNotNull(regHost)) redisProperties.setHost(regHost);
            if (Verification.checkNotNull(regPort)) redisProperties.setPort(Integer.parseInt(regPort));
            if (Verification.checkNotNull(regDb)) redisProperties.setDatabase(Integer.parseInt(regDb));

            SocketOptions socketOptions = SocketOptions.builder().connectTimeout(Duration.ofMillis(500)).build();
            ClientOptions clientOptions = ClientOptions.builder().socketOptions(socketOptions).build();
            RedisStandaloneConfiguration redisStandaloneConfiguration = getRedisStandaloneConfiguration(redisProperties);
            LettuceClientConfiguration clientConfig = LettucePoolingClientConfiguration.builder().commandTimeout(Duration.ofMillis(60000)).shutdownTimeout(shutdownTimeout).poolConfig(poolConfig).clientOptions(clientOptions).build();
            factory = new LettuceConnectionFactory(redisStandaloneConfiguration, clientConfig);
            redisProperties.setUrl(null);
        } else {
            logger.info("redis connect type: standalone");
            redisProperties.setHost(Transformation.nvl(redisProperties.getHost(), "localhost"));
            SocketOptions socketOptions = SocketOptions.builder().connectTimeout(Duration.ofMillis(500)).build();
            ClientOptions clientOptions = ClientOptions.builder().socketOptions(socketOptions).build();
            RedisStandaloneConfiguration redisStandaloneConfiguration = getRedisStandaloneConfiguration(redisProperties);
            LettuceClientConfiguration clientConfig = LettucePoolingClientConfiguration.builder().commandTimeout(Duration.ofMillis(60000)).shutdownTimeout(shutdownTimeout).poolConfig(poolConfig).clientOptions(clientOptions).build();
            factory = new LettuceConnectionFactory(redisStandaloneConfiguration, clientConfig);
        }
        final JSONObject properties = JSONUtils.toJSONObject(redisProperties);
        properties.put("password", Verification.checkNull(redisProperties.getPassword()) ? "" : "********");
        logger.info("redis configs:{}", JSONUtils.toJSON(properties, Feature.PRETTY_PRINT));
        factory.setShareNativeConnection(false);
        factory.setValidateConnection(true);
        factory.afterPropertiesSet();
        return factory;
    }

    public static RedisNode fromString(String hostPortString) {
        AssertUtils.notNull(hostPortString, "HostAndPort must not be null");
        String host;
        String portString = null;

        if (hostPortString.startsWith("[")) {
            String[] hostAndPort = getHostAndPortFromBracketedHost(hostPortString);
            host = hostAndPort[0];
            portString = hostAndPort[1];
        } else {
            int colonPos = hostPortString.indexOf(':');
            if (colonPos >= 0 && hostPortString.indexOf(':', colonPos + 1) == -1) {
                // Exactly 1 colon. Split into host:port.
                host = hostPortString.substring(0, colonPos);
                portString = hostPortString.substring(colonPos + 1);
            } else {
                // 0 or 2+ colons. Bare hostname or IPv6 literal.
                host = hostPortString;
            }
        }

        int port;
        try {
            port = Integer.parseInt(portString);
        } catch (RuntimeException ignore) {
            throw new IllegalArgumentException(String.format("Unparseable port number: %s", hostPortString));
        }

        if (!isValidPort(port)) {
            throw new IllegalArgumentException(String.format("Port number out of range: %s", hostPortString));
        }

        return new RedisNode(host, port);
    }

    private static boolean isValidPort(int port) {
        return port >= 0 && port <= 65535;
    }

    private static String[] getHostAndPortFromBracketedHost(String hostPortString) {

        if (hostPortString.charAt(0) != '[') {
            throw new IllegalArgumentException(String.format("Bracketed host-port string must start with a bracket: %s", hostPortString));
        }

        int colonIndex = hostPortString.indexOf(':');
        int closeBracketIndex = hostPortString.lastIndexOf(']');

        if (!(colonIndex > -1 && closeBracketIndex > colonIndex)) {
            throw new IllegalArgumentException(String.format("Invalid bracketed host/port: %s", hostPortString));
        }

        String host = hostPortString.substring(1, closeBracketIndex);
        if (closeBracketIndex + 1 == hostPortString.length()) {
            return new String[]{host, ""};
        } else {
            if (!(hostPortString.charAt(closeBracketIndex + 1) == ':')) {
                throw new IllegalArgumentException(String.format("Only a colon may follow a close bracket: %s", hostPortString));
            }
            for (int i = closeBracketIndex + 2; i < hostPortString.length(); ++i) {
                if (!Character.isDigit(hostPortString.charAt(i))) {
                    throw new IllegalArgumentException(String.format("Port must be numeric: %s", hostPortString));
                }
            }
            return new String[]{host, hostPortString.substring(closeBracketIndex + 2)};
        }
    }

    private static RedisStandaloneConfiguration getRedisStandaloneConfiguration(RedisProperties redisProperties) {
        String host = redisProperties.getHost();
        int port = redisProperties.getPort();
        int database = redisProperties.getDatabase();
        RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
        redisStandaloneConfiguration.setHostName(host);
        redisStandaloneConfiguration.setPort(port);
        redisStandaloneConfiguration.setDatabase(database);
        redisStandaloneConfiguration.setUsername(redisProperties.getUsername());
        redisStandaloneConfiguration.setPassword(redisProperties.getPassword());
        return redisStandaloneConfiguration;
    }

    public static boolean ping(RedisConnection connection) {
        try {
            connection.ping();
            connection.close();
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    public boolean ping() {
        RedisConnection connection = getFactory().getConnection();
        return ping(connection);
    }

    public static boolean ping(RedisConnectionFactory factory) {
        if (factory == null) {
            return false;
        }
        return ping(factory.getConnection());
    }

    public static LettuceConnectionFactory getRedisConnectionFactory(DBConnection connection) {
        LettuceConnectionFactory factory = redisConnectionFactoryCache.get(connection);

        if (factory == null) {
            factory = initRedisConnectionFactory(connection);
            if (ping(factory)) {
                removeRedisConnectionFactoryCache(connection);
                redisConnectionFactoryCache.put(connection, factory);
                logger.info("create new redis connection :" + connection);
            }
        }

        return factory;
    }

    private static void removeRedisConnectionFactoryCache(DBConnection connection) {
        redisConnectionFactoryCache.keySet().stream().filter(dbConnection -> dbConnection.getUrl().equals(connection.getUrl()) && dbConnection.getUsername().equals(connection.getUsername())).collect(Collectors.toList()).forEach(redisConnectionFactoryCache::remove);
    }

    public static LettuceConnectionFactory getRedisConnectionFactory(String url, String username, String password) {
        return getRedisConnectionFactory(new DBConnection(url, username, password));
    }

    public static StringRedisTemplate createRedisTemplate() {
        return new StringRedisTemplate();
    }

    public static StringRedisTemplate createRedisTemplate(DBConnection connection) {
        return createRedisTemplate(getRedisConnectionFactory(connection));
    }

    public static StringRedisTemplate createRedisTemplate(String url, String username, String password) {
        return createRedisTemplate(getRedisConnectionFactory(url, username, password));
    }

    public static StringRedisTemplate createRedisTemplate(RedisConnectionFactory factory) {
        return new StringRedisTemplate(factory);
    }
}
